using UnityEngine;
using System.Collections;

public enum eCoveringState
{
	Normal,
	Left,
	Right
};

public class sCovers : MonoBehaviour
{
	private static sCovers instance = null;

	private sCovers ()
	{
	}

	public static sCovers Instance
	{
		get
		{
			if (instance == null)
			{
				Debug.Log("instantiate cover functions helper");
                GameObject go = new GameObject();
                instance = go.AddComponent<sCovers>();
                go.name = "sCovers";
			}
			return instance;
		}
	}
	
	CharacterBehavior c;
	float m_fSecurityDistanceToCover = 0.5f;
	eCoveringState m_iState;
	
	public eCoveringState getCoveringState()
	{
		return m_iState;
	}
	
	public void Awake()
	{
		c = sPlayers.Instance.getBehavior();
	}
	
	public void initState()
	{
		Vector3 vNormal = -c.vTargetNormal;
		vNormal.y = 0.0f;
		c.setForward(vNormal, Vector3.up);
	}
	
	public bool updateShootingState()
	{
		if (!keepCoverPosition(m_fSecurityDistanceToCover))
		{
			c.changeState<CharacterStateStanding>();
			return true;
		}
		return false;
	}
	
	public bool updateState(float _fDistanceToCover, float _fCoveringMoveSpeed, float _fCoverHeight)
	{
		if (!keepCoverPosition(m_fSecurityDistanceToCover))
		{
			c.changeState<CharacterStateStanding>();
			return true;
		}
		
		updateCoveringMove(_fCoveringMoveSpeed, _fDistanceToCover);
				
		c.updateVerticalPosition();
		
		// adapt state to different cover heights
		if (updateCoverHeight(_fDistanceToCover))
		{
			return true;
		}
		
		// test if player shots out of the cover
		if (c.updateShotMode())
		{
			if (m_iState == eCoveringState.Left)
			{
				c.changeState<CharacterStateCoveringLowShotLeft>();
				return true;
			}
			else if (m_iState == eCoveringState.Right)
			{
				c.changeState<CharacterStateCoveringLowShotRight>();
				return true;
			}
		}
		
		return false;
	}
	
	Collider m_oCurrentCover;
	
	public void hitCover(float _fForce)
	{
		Rigidbody body = m_oCurrentCover.attachedRigidbody;
		
        if (body == null || body.isKinematic) return;
        
		body.AddForceAtPosition(-c.vTargetNormal * _fForce * 60.0f, c.vTargetPoint);
	}
	
	public bool findCovers(float _fDistanceToCover, float _fDistanceToCoverMomentum, float _fSecurityDistanceToCover)
	{
		Vector3 vDirection = c.getForward();
		float fAddedDistance = Input.GetAxis("Vertical") * _fDistanceToCoverMomentum;
		Vector3 p1 = c.getMiddlePos();
		Vector3 p2 = p1 + vDirection * (_fDistanceToCover + fAddedDistance);
		if (sPhysics.Instance.linecastVsCovers(p1, p2))
		{
			Vector3 vNormal = sPhysics.Instance.getLinecastNormal();
			m_oCurrentCover = sPhysics.Instance.getLinecastCollider();
			c.vTargetNormal = vNormal;
			
			Vector3 vTargetPos = sPhysics.Instance.getLinecastPoint() + (vNormal * _fSecurityDistanceToCover);
			c.vTargetPoint = sPhysics.Instance.getLinecastPoint();
			c.vTargetPosition = c.getGroundOfPoint(vTargetPos);
			
			c.changeState<CharacterStateToCover>();
			return true;
		}
		return false;
	}
	
	public void updateCoveringMove(float _fMoveSpeed, float _fDistanceToCover)
	{
		Vector2 vLeftStick = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
		
		if (vLeftStick.y < -0.8f)
		{
			c.changeState<CharacterStateUncovering>();
			return;
		}
		
		Vector3 vMove = Vector3.zero;
		if (vLeftStick.x < -0.1f)
		{
			vMove = -c.getRight() * _fMoveSpeed * Time.deltaTime;
			m_iState = eCoveringState.Left;
		}
		else if (vLeftStick.x > 0.1f)
		{
			vMove = c.getRight() * _fMoveSpeed * Time.deltaTime;
			m_iState = eCoveringState.Right;
		}
		
		if (vMove != Vector3.zero)
		{
			Vector3 vMoveDirection = vMove.normalized;
			Vector3 v1 = c.getMiddlePos() + vMove + vMoveDirection * 0.3f;
			Vector3 v2 = v1 + c.getForward() * _fDistanceToCover;
			// if there is enough cover to move
			if (sPhysics.Instance.linecastVsCovers(v1, v2)
				|| (m_iState == eCoveringState.Left && m_bKeepCoverRayHit[0])
				|| (m_iState == eCoveringState.Right && m_bKeepCoverRayHit[2]))
			{
				c.oController.Move(vMove);
				m_iState = eCoveringState.Normal;
			}
			else // if character is in the limit of the cover
			{
				bool bAvoidableGap = false;
				Vector3 vEdge = c.getCollisionEdgeAvoidingGaps(c.getMiddlePos(), v1, c.getForward(), _fDistanceToCover + 0.2f, 8, vMove.normalized, 0.5f, ref bAvoidableGap);
				if (!bAvoidableGap)
				{
					if (m_iState == eCoveringState.Normal)
					{
						c.oController.Move(vEdge - c.getMiddlePos());
					}
				}
				else
				{
					c.oController.Move(vMove);
					m_iState = eCoveringState.Normal;
				}
			}
		}
	}
	
	void OnControllerColliderHit(ControllerColliderHit hit)
	{	
		// see if the body collided with other than the current cover
		/*if (hit.gameObject.layer == LAYER_COVERS_INDEX)
		{
			if (hit.collider != m_oCurrentCover)
			{
				m_vNewCoverPoint = hit.point;
			}
			else
			{
				m_vNewCoverPoint = new Vector3(float.MinValue, float.MinValue, float.MinValue);
			}
		}*/
		
        /*Rigidbody body = hit.collider.attachedRigidbody;
        if (body == null || body.isKinematic)
            return;
        
        if (hit.moveDirection.y < -0.3F)
            return;
        
        Vector3 pushDir = new Vector3(hit.moveDirection.x, 0, hit.moveDirection.z);
        body.velocity = pushDir * pushPower;*/
    }
	
	Vector3 m_vForwardSmoothVelocity = Vector3.zero;
	Vector3[] m_vKeepCoverPoints = new Vector3[3];
	bool[] m_bKeepCoverRayHit = new bool[3];
	public bool keepCoverPosition(float _fSecurityDistanceToCover)
	{
		// throw 3 rays and get the mixed position and forward between all three
		Vector3 p0 = c.getMiddlePos() - c.getForward() * 0.6f;
		float fRayDistance = _fSecurityDistanceToCover + 1.8f;
		
		Vector3 vDirection2 = c.getForward();
		// get the direction of the left ray
		Vector3 vDirection1 = Quaternion.AngleAxis(-50.0f, Vector3.up) * vDirection2;
		Vector3 vDirection3 = Quaternion.AngleAxis(50.0f, Vector3.up) * vDirection2;
		
		m_vKeepCoverPoints[0] = p0 + vDirection1 * fRayDistance;
		m_vKeepCoverPoints[1] = p0 + vDirection2 * fRayDistance;
		m_vKeepCoverPoints[2] = p0 + vDirection3 * fRayDistance;
		
		c.renderLine(p0, m_vKeepCoverPoints[0]);
		c.renderLine(p0, m_vKeepCoverPoints[1]);
		c.renderLine(p0, m_vKeepCoverPoints[2]);
		
		int iRaysHit = 0;
		Vector3 vForward = Vector3.zero;
				
		for (int i=0; i<3; ++i)
		{
			if (sPhysics.Instance.linecastVsCovers(p0, m_vKeepCoverPoints[i]))
			{
				m_bKeepCoverRayHit[i] = true;
				++iRaysHit;
				float fDis = Vector3.Distance(p0, sPhysics.Instance.getLinecastPoint());
				float fWeight = fRayDistance - fDis;
				Vector3 vNewForward;
				vNewForward = -sPhysics.Instance.getLinecastNormal();
				if (fWeight > 0.0f && Vector3.Angle(vNewForward, c.getForward()) < 90.0f)
				{
					vForward += vNewForward * fWeight;
				}
				
				// if the middle ray hit, try to move the character to this position
				if (i==1)
				{
					// the middle ray counts double
					++iRaysHit;
					c.vTargetPosition = sPhysics.Instance.getLinecastPoint() + (sPhysics.Instance.getLinecastNormal() * _fSecurityDistanceToCover);
					c.vTargetPosition = c.getGroundOfPoint(c.vTargetPosition);
					//Vector3 vMove = c.vTargetPosition - c.getPos();
					//oController.Move(vMove * Time.deltaTime);
				}
			}
			else
			{
				m_bKeepCoverRayHit[i] = false;
			}
		}

		vForward = Vector3.SmoothDamp(c.getForward(), vForward, ref m_vForwardSmoothVelocity, 0.05f);
		vForward.y = 0.0f;
		vForward.Normalize();
		c.setForward(vForward, Vector3.up);
		
		return iRaysHit >= 2;
	}
	
	public bool updateCoverHeight(float _fDistanceToCover)
	{
		Vector3 vPlayer = c.getPos();
		vPlayer.y += 1.5f;
		bool bStand = false;
		if(sPhysics.Instance.linecastVsCovers(vPlayer, vPlayer + (c.getForward() * (_fDistanceToCover + 0.5f))))
		{
			bStand = true;
		}
		
		CharacterState cs = c.getState();
		
		if (cs is CharacterStateCoveringHigh)
		{
			if (!bStand)
			{
				c.changeState<CharacterStateCoveringLow>();
				return true;
			}
		}
		else if (cs is CharacterStateCoveringLow)
		{
			if (bStand)
			{
				c.changeState<CharacterStateCoveringHigh>();
				return true;
			}
		}
		return false;
	}
}
